package net.deiangi.graphs.ui;

import net.deiangi.graphs.core.Edge;
import net.deiangi.graphs.core.Graph;
import net.deiangi.graphs.core.Node;

import java.awt.*;

/**
 * User: Deian
 * Date: Oct 30, 2009
 * Time: 11:37:38 PM
 */
public class GraphRenderer {

    Container renderDestination;
    public Graph graph;

    Image offscreen;
    Dimension offscreensize;
    Graphics offgraphics;
    public boolean showWeight = false;
    public boolean showDirections = true;
    public boolean showLabels = true;


    public GraphRenderer(Container renderDestination, Graph graph) {
        this.renderDestination = renderDestination;
        this.graph = graph;
    }

    final Color fixedColor = Color.red;
    final Color selectColor = Color.green;
    final Color edgeColor = Color.black;
    final Color nodeColor = Color.orange;//new Color(250, 220, 100);
    final Color stressColor = Color.darkGray;
    final Color arcColor1 = Color.black;
    final Color arcColor2 = Color.pink;
    final Color arcColor3 = Color.red;

    public void paintNode(Graphics g, Node n, FontMetrics fm) {
        int x = (int) n.x;
        int y = (int) n.y;
        Color bgColor = (n.selected) ? selectColor : (n.fixed ? fixedColor : nodeColor);
        String lbl = n.toString();
        Dimension box = BoxedText(g, x, y, lbl, fm, bgColor, Color.black);
        if (n.note != null) {
            BoxedText(g, x + box.width + 5, y - 5 - box.height, n.note, fm, Color.white, Color.lightGray);
        }
    }

    private Dimension BoxedText(Graphics g, int x, int y, String lbl, FontMetrics fm, Color bgColor, Color borderColor) {
        int w = fm.stringWidth(lbl) + 10;
        int h = fm.getHeight() + 4;
        g.setColor(bgColor);
        g.fillRect(x - w / 2, y - h / 2, w, h);
        g.setColor(borderColor);
        g.drawRect(x - w / 2, y - h / 2, w - 1, h - 1);
        g.drawString(lbl, x - (w - 10) / 2, (y - (h - 4) / 2) + fm.getAscent());
        return new Dimension(w, h);
    }

    public synchronized void update(Graphics g) {
        Dimension d = renderDestination.getSize();
        if ((offscreen == null) || (d.width != offscreensize.width) || (d.height != offscreensize.height)) {
            offscreen = renderDestination.createImage(d.width, d.height);
            offscreensize = d;
            if (offgraphics != null) {
                offgraphics.dispose();
            }
            offgraphics = offscreen.getGraphics();
            offgraphics.setFont(renderDestination.getFont());
        }

        offgraphics.setColor(renderDestination.getBackground());
        offgraphics.fillRect(0, 0, d.width, d.height);

        for (Edge e : graph.edges) {
            Node fn = graph.nodes.get(e.fi);
            Node tn = graph.nodes.get(e.ti);
            int x1 = (int) fn.x;
            int y1 = (int) fn.y;
            int x2 = (int) tn.x;
            int y2 = (int) tn.y;
            int len = (int) Math.abs(Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) - e.w);
            offgraphics.setColor((len < 10) ? arcColor1 : (len < 20 ? arcColor2 : arcColor3));
            if (e.selected) {
                offgraphics.setColor(selectColor);
            }
            if (showDirections) {
                drawArrowedLine(offgraphics, x1, y1, x2, y2, 5);
            } else {
                offgraphics.drawLine(x1, y1, x2, y2);
            }

            String lbl = (e.note != null) ? String.format("%s", e.note) : "";
            if (showWeight) {
                lbl = String.format("%d(%d) %s", e.w, len, lbl);
            }
            if (lbl.length() > 0) {
                offgraphics.setColor(stressColor);
                offgraphics.drawString(lbl, x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2);
                offgraphics.setColor(edgeColor);
            }
        }

        if (showLabels) {
            FontMetrics fm = offgraphics.getFontMetrics();
            for (Node n : graph.nodes) {
                paintNode(offgraphics, n, fm);
            }
        }

        g.drawImage(offscreen, 0, 0, null);
    }

    public static void drawArrowedLine(Graphics g, int x1, int y1, int x2, int y2, float size) {
        double arrowDirection = Math.atan2(x1 - x2, y1 - y2);
        // draw the actual line 
        g.drawLine(x2, y2, x1, y1);

        // starting position of the arrow
        int kx = (x2 + x1) / 2;
        int ky = (y2 + y1) / 2;

        Polygon arrowShape = new Polygon();
        int wingSize = (int) (size * 3);
        int baseSize = (int) (size * 2);
        arrowShape.addPoint(kx, ky);// tip
        arrowShape.addPoint((int) (kx + wingSize * Math.sin(arrowDirection + .5)), (int) (ky + wingSize * Math.cos(arrowDirection + .5)));
        arrowShape.addPoint((int) (kx + baseSize * Math.sin(arrowDirection)), (int) (ky + baseSize * Math.cos(arrowDirection)));
        arrowShape.addPoint((int) (kx + wingSize * Math.sin(arrowDirection - .5)), (int) (ky + wingSize * Math.cos(arrowDirection - .5)));
        arrowShape.addPoint(kx, ky); // arrow tip
        g.drawPolygon(arrowShape);
        // can play with the filling if you need different color
        g.fillPolygon(arrowShape);
    }

    private static int yCor(int len, double dir) {
        return (int) (len * Math.cos(dir));
    }

    private static int xCor(int len, double dir) {
        return (int) (len * Math.sin(dir));
    }

    synchronized void relax() {
        // move the nodes to reach the desired W
        for (Edge e : graph.edges) {
            Node tn = graph.nodes.get(e.ti);
            Node fn = graph.nodes.get(e.fi);
            double vx = tn.x - fn.x;
            double vy = tn.y - fn.y;
            double len = Math.sqrt(vx * vx + vy * vy);
            len = (len == 0) ? .0001 : len;
            double f = (e.w - len) / (len * 3);
            double dx = f * vx;
            double dy = f * vy;

            tn.dx += dx;
            tn.dy += dy;
            fn.dx += -dx;
            fn.dy += -dy;
        }

        // move the nodes so to avoid the node overlapping
        for (Node n1 : graph.nodes) {
            double dx = 0;
            double dy = 0;

            for (Node n2 : graph.nodes) {
                if (n1 == n2) {
                    continue;
                }
                double vx = n1.x - n2.x;
                double vy = n1.y - n2.y;
                double len = vx * vx + vy * vy;
                if (len == 0) {
                    dx += Math.random();
                    dy += Math.random();
                } else if (len < 100 * 100) {
                    dx += vx / len;
                    dy += vy / len;
                }
            }
            double dlen = dx * dx + dy * dy;
            if (dlen > 0) {
                dlen = Math.sqrt(dlen) / 2;
                n1.dx += dx / dlen;
                n1.dy += dy / dlen;
            }
        }

        Dimension d = renderDestination.getSize();
        for (Node n : graph.nodes) {
            if (!n.fixed) {
                n.x += Math.max(-5, Math.min(5, n.dx));
                n.y += Math.max(-5, Math.min(5, n.dy));
            }
            if (n.x < 0) {
                n.x = 0;
            } else if (n.x > d.width) {
                n.x = d.width;
            }
            if (n.y < 0) {
                n.y = 0;
            } else if (n.y > d.height) {
                n.y = d.height;
            }
            n.dx /= 2;
            n.dy /= 2;
        }
        renderDestination.repaint();
    }
}
